/** RMI Step 2 & 3: Implements the user-defined remote server 

    Step 2: The implementation of a user-defined remote server object include:
	- extending java.server.RemoteObject or a subclass of RemoteObject.
	  The commonly used RemoteObject's subclasses are: Acativatable and
	  UnicastRemoteObject. The Activatable allows system to activate
	  remote objects. UnicastRemoteObject defines a non-replicated object
	  that is running only when client process is alive.
	- implmenting the ChevalDBServer interface;
	- provideing services and returns data/messages through methods
	  defined in remote interface.
   Step 3: Write a remote server object application
	- The main functionality of the remote object server appliction includes
	  (1) Defining an object of the user-defined remote object.
	  (2) Binding/Rebindinga the remote object to a name unique in the
	      namespace so that remote client applications can refer to it.
   	- There are two different ways of writing the remote object server 
	  application
	  (1) Add a main() function that define a remote object and bind
	      the object to a name in Step 2.
	  (2) Write another class to start with main() to define an remote
	      server object and to bind it to name.
	Since the remote object server application performs only two simple
task, combining remote server implementation and remote server object application
is a better choice.
----------------------------------------------------------------------------------
   Step 4: Compile the server application:
		javac 	ChevalDBServer.java
   Step 5: Generate sub class:
		rmic 	ChevalDBServer
   Step 6: Run the rmiregistry server utility that allow system to
	   manage the remote object:
		rmiregistry &
   Step 7: Run the remote application that define a remote object and bind a
           unique name to the remote object
		java ChevalDBServerImpl &
----------------------------------------------------------------------------------
  Spcifics of this program:
   - The program starts a single database connection when an remote server object
     is defined the application server. The connection is shared by more all
     remote client programs.
   - To allow one client program to complete the its calls before the other remote
     calls can be done, thread and thread (competitive) synchronization is used.
     Without the synchronization, the data sent to a client program may be
     incorrect or incomplet when more than one client call the same remote object.

   To increase response time, multiple connections to database can be established,
and the connection pooling method can be used to pick up unused connection for a
client program's request.

*/
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.sql.*;

public class ChevalDBServerImpl extends UnicastRemoteObject implements ChevalDBServer,
	Serializable {
   DBAccessMonitor		mtr;
   String			heading;
   private Connection		cnn;
   String url = "jdbc:oracle:thin:@cheval:1521:TEST";
   String usr = "CS440" ;
   String pwd = "student1" ;
   static int    hitCount = 1;

   public ChevalDBServerImpl() throws RemoteException  {
	if ( cnn == null ) connectToDatabase();
	mtr = new DBAccessMonitor(cnn);
   }

   public Vector getResultSet(String sql)  throws RemoteException {
	Vector v = new Vector(100);
	Thread t = new DBAccessThread(mtr, sql, v );
	t.start();
	try { t.join(); } catch (InterruptedException e) {}
	// System.out.println("Number of items in vector = " + v.size() );
	return v;
   }

   private void connectToDatabase() {

        // System.err.println( "Connecting to database..." );

	try { DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
	} catch(SQLException e) {
			System.err.print("ClassNotFoundException: ");
			System.err.println(e.getMessage());}
	try {
		cnn = DriverManager.getConnection(url, usr, pwd) ;
		System.err.println("Connected to database!");
	} catch( SQLException e ) { System.err.println(e.toString()) ; }
          catch( Exception e ) { e.printStackTrace(); }
   }

   public static void main( String args[] ) throws Exception
   {     
      System.err.println(
         "Initializing DB server: please wait." );

      // create server object
      ChevalDBServerImpl temp = new ChevalDBServerImpl();

      // bind ChevalDBServerImpl object to the rmiregistry
      String serverObjectName = "//localhost/ChevalDBServer";
      Naming.rebind( serverObjectName, temp );
      // System.err.println( "The "+ serverObjectName + " is up and running." );
   }

}

class DBAccessThread extends Thread {
   DBAccessMonitor		mtr = null;
   String			sql = null;
   Vector			vResultSet = null;

   public DBAccessThread ( DBAccessMonitor m, String q, Vector v ) {
	mtr = m; sql = q; vResultSet = v;
	// System.out.println("Thread initialization ...");
   }
   public void run() {
     try { 
	mtr.getResultSet(sql, vResultSet);
     } catch ( RemoteException e) {}
   }
}

class DBAccessMonitor {
   private Connection 		cnn;
   private Statement		stmt = null;

   public DBAccessMonitor ( Connection c) 
   {	cnn = c;
	try {
		stmt = cnn.createStatement() ; 
		// System.out.println("Monitor initialization ...");
	} catch ( SQLException e) {}
	
   }
   
   public void finalize() { 
	try { stmt.close(); cnn.close(); }
	catch (SQLException e) { System.err.println(e.toString()); }
   }

   synchronized public void getResultSet ( String sql, Vector rows )  throws RemoteException
   {
	try {
		System.out.println("Call monitor's getResultSet ...");

		String SQL = null;
		if ( sql != null ) SQL = sql.toUpperCase();

		{ int k = sql.lastIndexOf(";");
		  if ( k > 0 ) sql = sql.substring(0, k);
		}

		if (SQL.lastIndexOf("DELETE")>=0 ) sql = null;
		if (SQL.lastIndexOf("UPDATE")>=0 ) sql = null;
		if (SQL.lastIndexOf("SELECT")< 0 ) sql = null;

		if ( sql == null ) sql = "SELECT * FROM AddressBook";

		if ( rows != null ) rows.clear();
		ResultSet rs = stmt.executeQuery(sql) ;
		ResultSetMetaData mdata = rs.getMetaData();
		int colCnt = mdata.getColumnCount();
		DBRow lenRow = new DBRow(colCnt), row = new DBRow(colCnt);
		
		// get column heading and their display lengths.
		for ( int i = 1; i <= colCnt; i ++ ) {
			int len = mdata.getColumnDisplaySize(i);
			switch( mdata.getColumnType(i) ) {
		   		case Types.NUMERIC: len = 10; break;
		   		case Types.DECIMAL: len = 10; break;
		   		case Types.DATE:    len = 10; break;
		   		case Types.TIME:    len = 10; break;
		   		case Types.TIMESTAMP: len = 10; break;
			}
			lenRow.setCol(i-1, len + "");
			row.setCol(i-1, mdata.getColumnName(i));
		}
		rows.addElement(lenRow); rows.addElement(row);

		// Get rows and coumns.
		int k = 2;
		String s ;
		while ( rs.next() ) {
		    row = new DBRow(colCnt);
		    for (int i = 1; i <= colCnt; i ++) {
			s = rs.getString(i);
			if ( s == null ) row.setCol(i-1, "");
			else if ( s.length() > Integer.parseInt(lenRow.getCol(i-1)) )
		    		row.setCol(i-1, s.substring(0,
					Integer.parseInt(lenRow.getCol(i-1))));
			     else {
		    		row.setCol(i-1, rs.getString(i) + "");
				// System.out.print ( rs.getString(i) );
			     }
		    }
		    rows.addElement(row);
		    k++;
		}
		// System.out.println("Number of rows in monitor = " + rows.size() );
		rs.close(); rows.trimToSize();
		System.out.println(Calendar.getInstance().getTime().toString() +
					" Request No: " + ChevalDBServerImpl.hitCount++);
	} catch ( SQLException e ) {
	     System.err.println ("Error in getResultSet(): " +e.toString());
	     System.err.println("SQL Statement: " + sql); }
   }

}
